﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using Seagull.Configuration;
using Seagull.DAL.DriveManagers;
using Seagull.Generic;
using Seagull.Model;

namespace Seagull.DAL
{
    public class PathHelper
    {
        public const int FileNameLength = 10;
        public const string DateFormatString = "yyyyMMdd";
        public const string ChannelFormatString = "CH{0}";
        public const string TimeFormatString = "HHmmss";
        public const string TimeFormatStringForTimespan = "hhmmss";
        public const string HourFormatString = "{0:D2}0000";
        private const int MaxHour = 23;
        private const int MinHour = 0;
        private readonly string _dataFileExtension;

        public PathHelper(string basePath, string dataFileExtension = null)
        {
            BasePath = basePath;
            _dataFileExtension = dataFileExtension ?? Config.Instance.PathConfig.DataFileExtension;
        }

        public string BasePath { get; private set; }

        #region List Features

        /// <summary>
        ///     取得目录下所有以日期命名的目录及其日期。按日期递增排序。
        /// </summary>
        public IEnumerable<Tuple<DirectoryInfo, DateTime>> DateDirectories
        {
            get
            {
                try
                {
                    return new DirectoryInfo(BasePath).EnumerateDirectories()
                        .Select(p =>
                        {
                            DateTime date;
                            if(DateTime.TryParseExact(p.Name, DateFormatString,
                                CultureInfo.InvariantCulture, DateTimeStyles.None,
                                out date))
                            {
                                return Tuple.Create(p, date);
                            }
                            return Tuple.Create(p, DateTime.MinValue);
                        })
                        .Where(p => p.Item2 != DateTime.MinValue)
                        .OrderBy(p => p.Item2);
                }
                catch(IOException)
                {
                    return Enumerable.Empty<Tuple<DirectoryInfo, DateTime>>();
                }
            }
        }

        public IEnumerable<Segment> ListAllSegments()
        {
            return DateDirectories.SelectMany(
                dateDir =>
                    ListChannels(dateDir.Item2).Where(t => Config.Instance.ChannelConfig.HasChannel(t.Item2)).SelectMany(
                        channelDir =>
                            ListSegments(dateDir.Item2, channelDir.Item2).Select(
                                segFile => new Segment(channelDir.Item2, dateDir.Item2 + segFile.Item2))));
        }

        public Tuple<DirectoryInfo, byte>[] ListChannels(DateTime date)
        {
            return GetDateDirectory(date)
                .EnumerateDirectories(ChannelFormatString.Replace("{0}", "*"), SearchOption.TopDirectoryOnly)
                .Select(t => Tuple.Create(t, byte.Parse(t.Name.Substring(2))))
                .ToArray();
        }

        /// <summary>
        ///     扩展名会被考虑
        /// </summary>
        /// <param name="date"> </param>
        /// <param name="channel"> </param>
        /// <returns> </returns>
        public Tuple<FileInfo, TimeSpan>[] ListSegments(DateTime date, byte channel)
        {
            return ListSegmentFiles(date, channel).Select(t =>
            {
                TimeSpan startTime;
                var success = TimeSpan.TryParseExact(
                    Path.GetFileNameWithoutExtension(t.Name),
                    TimeFormatStringForTimespan,
                    CultureInfo.InvariantCulture, out startTime);
                if(!success)
                {
                    startTime = TimeSpan.MaxValue;
                }
                return Tuple.Create(t, startTime);
            }).Where(p =>
                p.Item2 != TimeSpan.MaxValue &&
                    // 排除非整点的文件
                IsOnHour(p.Item2))
            .OrderBy(t => t.Item2).ToArray();
        }

        private static bool IsOnHour(TimeSpan time)
        {
            return time.Minutes == 0 && time.Seconds == 0;
        }

        private FileInfo[] ListSegmentFiles(DateTime date, byte channel)
        {
            return GetChannelDirectory(date, channel).GetFiles("*" + _dataFileExtension, SearchOption.TopDirectoryOnly);
        }

        #endregion

        #region Date

        public DirectoryInfo GetDateDirectory(DateTime date)
        {
            return GetDateDirectory(BasePath, date);
        }

        public static DirectoryInfo GetDateDirectory(string basePath, DateTime date)
        {
            return GetDirectoryInfo(Path.Combine(basePath, date.ToString(DateFormatString)));
        }

        #endregion

        #region Channel

        public DirectoryInfo GetChannelDirectory(DateTime date, byte channel)
        {
            return GetChannelDirectory(BasePath, date, channel);
        }

        public static DirectoryInfo GetChannelDirectory(string basePath, DateTime date, byte channel)
        {
            return GetDirectoryInfo(
                Path.Combine(basePath, date.ToString(DateFormatString), string.Format(ChannelFormatString, channel)));
        }

        #endregion

        #region Segment File

        public FileInfo GetSegmentFile(Segment segment)
        {
            return new FileInfo(Path.Combine(BasePath, GetRelativeSegmentFileName(segment)));
        }

        #endregion


        #region Hour file name without path

        public string GetHourFileNameWithoutPath(int hour)
        {
            return GetHourFileNameWithoutPath(hour, _dataFileExtension);
        }

        public static string GetHourFileNameWithoutPath(int hour, string dataFileExtension)
        {
            return string.Format(HourFormatString, hour) + dataFileExtension;
        }

        #endregion


        #region Relative Segment file name

        public string GetRelativeSegmentFileName(Segment segment)
        {
            return GetRelativeSegmentFileName(segment.StartTime, segment.Channel, _dataFileExtension);
        }

        public static string GetRelativeSegmentFileName(Segment segment, string dataFileExtension)
        {
            return GetRelativeSegmentFileName(segment.StartTime, segment.Channel, dataFileExtension);
        }

        public static string GetRelativeSegmentFileName(DateTime startTime, byte channel, string dataFileExtension)
        {
            return Path.Combine(startTime.ToString(DateFormatString),
                string.Format(ChannelFormatString, channel),
                GetHourFileNameWithoutPath(startTime.Hour, dataFileExtension));
        }

        #endregion

        #region Misc Features

        public static FileInfo GetChannelStatFile(byte channel)
        {
            // Sample: "..\Stat\127.0.0.1\Stat\CH1.stat"
            return new FileInfo(
                Path.Combine(
                    Config.Instance.PathConfig.ChannelStatFolder,
                    string.Format(ChannelFormatString, channel) + Config.Instance.PathConfig.StatFileExtension));
        }

        public static DriveInfo GetRemovableDrive()
        {
            var removableDrive = RemovableDriveManager.Instance.Drive;
            if(removableDrive == null)
            {
                throw new DriveNotFoundException("找不到移动硬盘。");
            }
            return removableDrive;
        }

        public static void EnsureDirectoryRecursively(DirectoryInfo directory)
        {
            if(directory == null)
                throw new ArgumentNullException("directory");
            directory.Create();
        }

        /// <summary>
        ///     获取移动硬盘上备份目录的路径。若该目录不存在则创建之。
        /// </summary>
        /// <param name="dataFolderNameOnRemovableDevice"> </param>
        /// <returns> </returns>
        public static string GetBackupDirectory(string dataFolderNameOnRemovableDevice)
        {
            var path = Path.Combine(
                GetRemovableDrive().RootDirectory.FullName,
                dataFolderNameOnRemovableDevice);
            if(!Directory.Exists(path))
            {
                FileHelper.CreateDirectory(path);
            }
            return GenericHelper.NormalizeFolderPath(path);
        }

        private static DirectoryInfo GetDirectoryInfo(string path)
        {
            return new DirectoryInfo(GenericHelper.NormalizeFolderPath(path));
        }

        #endregion

    }
}